#pragma once

namespace IDK
{
	value class Radian;
	value class Degree;

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Radian
	{
	public:
		float value;
		Radian(float rad) : value(rad)
		{}

		Radian(Degree deg);
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Degree
	{
	public:
		float value;
		Degree(float rad) : value(rad)
		{}

		Degree(Radian deg);
	};

	inline Radian::Radian(Degree deg)
	{
		value = UnManaged::System::Math::ToRadian(deg.value);
	}

	inline Degree::Degree(Radian rad)
	{
		value = UnManaged::System::Math::ToDegree(rad.value);
	}

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Vector2
	{
	public:
		typedef UnManaged::System::Math::Vector2 Self;

		float x;
		float y;

		Vector2(float v) : x(v), y(v)
		{}
		Vector2(float x, float y) : x(x), y(y)
		{}
		Vector2(const Self& vxy) : x(vxy.x), y(vxy.y)
		{}

		Self GetSelf()
		{
			const float vx = x;
			const float vy = y;
			return Self(vx, vy);
		}

		static Vector2 Zero = Vector2(0);
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Vector3
	{
	private:
		float mx, my, mz;
	public:
		typedef UnManaged::System::Math::Vector3 Self;

		Vector3(float v) : mx(v), my(v), mz(v)
		{}

		Vector3(float x, float y, float z) : mx(x), my(y), mz(z)
		{}
		Vector3(const Self& vxyz) : mx(vxyz.x), my(vxyz.y), mz(vxyz.z)
		{}

		Self GetSelf()
		{
			const float vx = mx;
			const float vy = my;
			const float vz = mz;
			return Self(vx, vy, vz);
		}

		property float x
		{ 
			float get();
			void set(float value);
		}

		property float y
		{ 
			float get();
			void set(float value);
		}

		property float z
		{ 
			float get();
			void set(float value);
		}
		
		Vector3 Getter()
		{
			return Vector3(x, y, z);
		}

		void Setter(Vector3 copy)
		{
			x = copy.x;
			y = copy.y;
			z = copy.z;
		}

		float Sqr()
		{
			return UnManaged::System::Math::Sqr(GetSelf());
		}

		float Length()
		{ 
			return UnManaged::System::Math::Length(GetSelf());
		}

		float Distance(Vector3 dest)
		{ 
			return GetDistance(*this, dest);
		}

		static Vector3 Normalize(Vector3 v)
		{
			return Vector3(UnManaged::System::Math::Normalize(v.GetSelf()));
		}

		static float Dot(Vector3 a, Vector3 b)
		{
			return UnManaged::System::Math::Dot(a.GetSelf(), b.GetSelf());
		}

		static Vector3 Cross(Vector3 a, Vector3 b)
		{
			return Vector3(UnManaged::System::Math::Cross(a.GetSelf(), b.GetSelf()));
		}

		static Vector3 Lerp(Vector3 a, Vector3 b, float t)
		{
			return Vector3(UnManaged::System::Math::Lerp(a.GetSelf(), b.GetSelf(), t));
		}

		static float GetDistance(Vector3 a, Vector3 b)
		{
			return UnManaged::System::Math::Distance(a.GetSelf(), b.GetSelf());
		}

		static Vector3 operator + (Vector3 left, Vector3 right)
		{
			return Vector3(left.x + right.x, left.y + right.y, left.z + right.z);
		}

		static Vector3 operator - (Vector3 left, Vector3 right)
		{
			return Vector3(left.x - right.x, left.y - right.y, left.z - right.z);
		}

		static Vector3 operator * (Vector3 left, float right)
		{
			return Vector3(left.x * right, left.y * right, left.z * right);
		}

		static Vector3 Infinity = Vector3(UnManaged::System::Math::FloatInfinity);
		static Vector3 NegInfinity = Vector3(UnManaged::System::Math::FloatNegInfinity);

		static Vector3 Zero = Vector3(0);
		static Vector3 One = Vector3(1);
		static Vector3 UpX = Vector3(1, 0, 0);
		static Vector3 UpY = Vector3(0, 1, 0);
		static Vector3 UpZ = Vector3(0, 0, 1);
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class AABBox
	{
	public:
		typedef UnManaged::System::Math::AABB Self;

		AABBox(Vector3 min, Vector3 max)
			: Min(min), Max(max)
		{}
		AABBox(const Self& copy)
			: Min(copy.min), Max(copy.max)
		{}
	public:
		Vector3	Min;
		Vector3	Max;

		property float Width { float get(); }
		property float Height { float get(); }
		property float Depth { float get(); }

		property float Radius 
		{
			float get()
			{
				return (Max - Min).Length();
			}
		}

		Self GetSelf()
		{
			return Self(Min.GetSelf(), Max.GetSelf());
		}

		static AABBox Infinity = AABBox(Vector3::NegInfinity, Vector3::Infinity);
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Quaternion
	{
	public:
		typedef UnManaged::System::Math::Quaternion Self;

		Quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z)
		{}
		Quaternion(const Self& quat) : w(quat.w), x(quat.x), y(quat.y), z(quat.z)
		{}

		//Apply rotatation
		static Vector3 Multiply(Quaternion left, Vector3 right)
		{
			return Vector3(left.GetSelf() * right.GetSelf());
		}

		static Quaternion Multiply(Quaternion left, Quaternion right)
		{
			return Quaternion(left.GetSelf() * right.GetSelf());
		}

		static property Quaternion Identity { Quaternion get(); }
		
		//[Flags]
		enum struct InterpolationMode
		{
			Linear,
			LinearSpherical
		};

		static Quaternion Lerp(InterpolationMode mode, Quaternion from, Quaternion to, float weight)
		{
			switch(mode)
			{
			case InterpolationMode::Linear:
				return Quaternion(UnManaged::System::Math::Lerp(from.GetSelf(), to.GetSelf(), weight));
			case InterpolationMode::LinearSpherical:
				return Quaternion(UnManaged::System::Math::SLerp(from.GetSelf(), to.GetSelf(), weight));
			default:
				throw;
			}
		}

		static Quaternion Inverse(Quaternion lhs)
		{
			return Quaternion(UnManaged::System::Math::Inverse(lhs.GetSelf()));
		}

		static Quaternion RotationTo(Vector3 axis, Vector3 direction)
		{
			const Self rotTo = UnManaged::System::Math::Inverse(UnManaged::System::Math::RotationTo(axis.GetSelf(), direction.GetSelf()));
			return Quaternion(rotTo);
		}

		static Quaternion Rotate(Vector3 axis, Radian angle)
		{
			return Quaternion(UnManaged::System::Math::Rotate(axis.GetSelf(), UnManaged::System::Math::Radian(angle.value)));
		}

		static Quaternion Rotate(Quaternion q, Vector3 axis, Radian angle)
		{
			return Quaternion(q.GetSelf() * UnManaged::System::Math::Rotate(axis.GetSelf(),
				UnManaged::System::Math::Radian(angle.value)));
		}

		/**
		help function for arcball 
		orthogonal projection on sphere of radius 1, 
		standing in (x = 0, y = 0)
		*/
		static Vector3 OrthoProjectOnSphere(Vector2 xy)
		{
			Vector3 p(xy.x, xy.y, 0);
			float ls = p.Sqr();
			if (ls >= 1.0f)
				return Vector3::Normalize(p);
			else
				p.z = UnManaged::System::Math::Sqrt(1.0f - ls);

			return p;
		}

		static Quaternion ShortestArc(Vector3 from, Vector3 to)
		{
			return Quaternion(UnManaged::System::Math::ShortestArc(from.GetSelf(), to.GetSelf()));
		}

		static Quaternion TrackBall(Quaternion q, Vector2 from, Vector2 to)
		{
			return Quaternion(q.GetSelf() * ShortestArc(OrthoProjectOnSphere(from), OrthoProjectOnSphere(to)).GetSelf());
		}

		 Self GetSelf()
		 {
			const float qx = x;
			const float qy = y;
			const float qz = z;
			const float qw = w;
			return Self(qw, UnManaged::System::Math::Vector3(qx, qy, qz));
		 }
	public:
		float x, y, z, w;
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Plane
	{
	public:
		typedef UnManaged::System::Math::Plane Self;
		Vector3 Normal;
		float Distance;

		Plane(Vector3 normal, float distance) 
			: Normal(normal), Distance(distance)
		{}

		Self GetSelf()
		{
			return Self(Normal.GetSelf(), Distance);
		}
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Sphere
	{
	public:
		typedef UnManaged::System::Math::Sphere Self;
		Vector3 Center;
		float Radius;

		Sphere(Vector3 center, float radius) 
			: Center(center), Radius(radius)
		{}

		Self GetSelf()
		{
			return Self(Center.GetSelf(), Radius);
		}
	};

	[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Sequential, Pack = 4)]
	public value class Ray
	{
	public:
		typedef UnManaged::System::Math::Ray Self;
		Vector3 Orign;
		Vector3 Direction;

		Ray(Vector3 orign, Vector3 direction) : Orign(orign), Direction(direction)
		{}

		Vector3 Intersect(Plane plane, float offsetHeight)
		{
			return Vector3(GetSelf().Intersect(plane.GetSelf(), offsetHeight));
		}

		Vector3 Intersect(Sphere sphere)
		{
			return Vector3(GetSelf().Intersect(sphere.GetSelf()));
		}

		Self GetSelf()
		{
			return Self(Orign.GetSelf(), Direction.GetSelf());
		}
	};

	inline Quaternion Quaternion::Identity::get()
	{
		Quaternion result(1.0f, 0.0f, 0.0f, 0.0f);
		return result;
	}

	inline float AABBox::Width::get()
	{
		return (Max.x - Min.x);
	}

	inline float AABBox::Height::get()
	{
		return (Max.y - Min.y);
	}

	inline float AABBox::Depth::get()
	{
		return (Max.z - Min.z);
	}

	//
	inline float Vector3::x::get()
	{
		return mx;
	}
	inline void Vector3::x::set(float value)
	{
		mx = value;
	}

	inline float Vector3::y::get()
	{
		return my;
	}
	inline void Vector3::y::set(float value)
	{
		my = value;
	}

	inline float Vector3::z::get()
	{
		return mz;
	}
	inline void Vector3::z::set(float value)
	{
		mz = value;
	}
}